iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0
Software Development

Oops! OOPP: An Introduction to Object-Oriented Programming in Python系列 第 17

To Self, or Not To Self: That Is the Question

  • 分享至 

  • xImage
  •  

之前十幾篇端出連串理論和術語的「大魚大肉」,讀者可能已經吃膩。今天換碟清粥小菜,幫大家清清腸胃。

這碟清粥小菜,是 selfcls

self vs cls

  • 先複習一下selfcls這兩個參數的不同使用場合:
    • 如前幾篇所述,Python類別中,實例方法(instance methods)的第一個參數,「必須」是大家熟知又常忘記寫的 self
    • 而較為陌生的類別方法(class methods),其第一個參數,通常名為 cls。各位應該輕易猜到,這cls就是class的縮寫。

self

  • self是傳入物件的「本尊」。這應該是Python的ABC了,我猜大概隨便一本Python的教科書都有提及。所謂「本尊」,講白點就是在記憶體中的位置,即用id()函數取得的值。
  • 看code吧,先證明「本尊」論是否正確。不過這好像證明太陽從東邊昇起:
    class Tree():
        def __init__(self, breed, age):
            self.__breed = breed  
            self.__age = age
            print(f'class : {id(self)=}')
    
    tree = Tree('cedar', 200)
    print(f'parent: {id(tree)=}')
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221002/20148485MgElR9jACz.png
  • 結果:「本尊論」成立。

cls

  • cls則是「類別本身」。亦即類別在記憶體中的位置,同樣可用id()函數檢查:
    class Tree():
        __count = 0         # 放在constructor外面的是class attributes。
    
        def __init__(self, breed: str, age: int):   
            self.__breed = breed
            self.__age = age
    
            Tree.__count += 1       
    
        @classmethod
        def show_class_id(cls):
            print(f'class : {id(cls) =}')
    
    
    tree = Tree('cedar', 200)
    print(f'parent: {id(Tree)=}')
    Tree.show_class_id()
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221002/20148485YWDu5UoBqT.png
  • 主程式中的Tree()位址也和類別方法中的cls位址相同,表示筆者所言不虛。

不一定非self不可

  • 本節講的 self 包括 cls

  • 其實不只是Python,其他一些物件導向程式語言也會用self或其他字眼代表物件本尊。大體上泰半程式語言非selfthis

  • 其他語言這個selfthis多半是強制的,而Python的self卻僅是「慣例」。

  • 再引用一次PEP 8有關selfcls的規範:

    Always use self for the first argument to instance methods.
    Always use cls for the first argument to class methods.

  • 會在PEP 8上規範,就知道不是強制了。

  • 既然並未取得語法層面的「法定地位」,就表示不一定要用 self。事實上任何Python的合法變數名稱都可以。

  • 所以您真看self不順眼,或覺得self不吉利,可隨意改用諸如this, that, it, Me, Current...等等。至於效果會不會比機房放乖乖好,不得而知。

  • 甚至用更「無厘頭」的a, b, c, x, y, z或者您自己的大名也行。當然這種命名方法可不可取,是另一個議題了。請記住我們不是在寫擾亂器(obfuscator)。

  • 證明:

    class Tree():
    
        # __count = 0         # 放在constructor外面的是class attributes。
        def __init__(this, breed):   # use 'this' instead of 'self'.
            this.__breed = breed
    
        def show_myself(that):       # use 'that' instead of 'self'.
            print(f'{id(that) = }')
    
        def get_id(Tree):            # Even class name 'Tree' is fine.
            return id(Tree)
    
        @classmethod
        def show_class_id(alex):
            print(f'class : {id(alex) =}')
    
    
    tree = Tree('cedar') 
    Tree.show_class_id()
    
  • 執行上面的code,無任何錯誤(輸出甚麼不重要),表示用this, that, Tree, alex...等等,在語法層面都是合法的。

  • 真正的大問題在於:只要用到selfcls以外的名稱(注意兩者使用時機不同,不能混用),您就觸犯了PEP 8金科玉律。「問斬」倒不必,coding風格和其他大多數人不一樣則是肯定。不知這叫「別具創意」還是「標新立異」?

  • 就看您自己取捨了。問筆者的話,我會強烈建議「西瓜靠大邊」,從眾用self。筆者自己做專案打死也是selfself去的,謝絕其他字眼。

  • 有人說coding是「藝術」,筆者從不相信。coding只是「工藝品」或「工業產品」,得遵守一定的規格和標準。Coding style不宜帶有強烈獨突的「個人風格和色彩」,最少筆者不能接受。

self非Python保留字

  • self(含cls,下同)這個名稱可以換用其他字眼,言外之意就是:self並不是Python的保留字。而C++, Java, C#, JS這掛語言,和self意義相當的this卻是保留字。

  • Python只有35個保留字(註1)。C++有95個,Java 97個,C#較少,也有79個。號稱「最純」物件導向程式語言Smalltalk的保留字更少,只有5或6個。站在方便寫程式的角度,似乎越少保留字,為變數、物件命名的自由度越大。但站在其他角度看問題,就不一定越少越好。

  • 正因為self並非Python的保留字,所以必須在方法定義的參數列外顯標明self作為第一個「形參」(formal parameter)(註2)。C-like語言則不需標明this,因為那是人家的保留字。

  • 弔詭的是,主程式呼叫(調用)方法時,「實參」(actual argument)卻不必傳出self或其他變數。這個「本尊」由Python幫我們自動傳給方法。

  • 結果就是目前的情形:類別中的方法,形參永遠要比實參多出一個。而一般的函數,實參和形參數目相等。當然這裡先跳過不談預設參數,*參數,**參數等情形。

  • 請看下面的code:

    class Tree():
        def __init__(self, breed):
            self.__breed = breed
    
        def get_breed(self):
            return self.__breed
    
    
    tree = Tree('cedar')    # 我們只傳一個參數給Tree(),其實Python幫我們傳了兩個。
    print(f'{id(tree)=}')
    print(f"tree.breed: {tree.get_breed()}")    # 表面上沒有傳參數給get_bread(),其實Python內部傳出了物件本尊。
    
    • tree = Tree('cedar')這行,我們只傳一個參數給Tree(),其實Python幫我們傳了兩個。
    • print(f"tree.breed: {tree.get_breed()}")這行,表面上沒有傳參數給get_bread(),其實Python內部傳出了物件本尊。

這個設計是好是壞?

  • 好處:
    • selfcls兩個保留字。
    • 可隨個人/團隊喜愛而更改,彈性十足。
  • 壞處:
    • 實參和形參數目不等,形參比實參多一,但而一般非類別的函數卻形實兩參數目相同。同為「函數」做法有異,缺乏一致性。
    • 如真用不同名稱,會讓別人看code不便,影響溝通。
  • 愚見:
    • 個人比較喜歡Python改將selfcls列為保留字,這樣函數定義地方就不必寫self或cls。形參和實參數目也相等。或者統一用另外一字例如this取代selfcls也行。
    • 但如此一改,就會和之前的code不相容,茲事體大。真提出這個建議,大概連pre-PEP這關都沒機會通過。
    • 改變Python既不可能,只得改變自己心態,大方接受。

註1: 下表是Python的35個保留字。一般找到的表格都按字母排列,筆者覺得不甚理想,改依性質和功能重新整理排列,方便大家查閱:

True False None from import as with
global nonlocal is in and or not
if else elif for while break continue
def return yield lambda class pass del
try except finally raise assert async await

註2: 程式語言中的「實參」和「形參」說明:

  • 「實參」是「實際參數」的縮寫。是指呼叫函數時,傳給函數的實際資料。英文是actual argument。
  • 「形參」是「形式參數」的縮寫。是指函數定義時可接受的參數。英文為formal parameter。
  • 其實參數的兩個英文argumentparameter可以混用,也常常混用。不管實參或者形參,都可以稱作argument或parameter。筆者認為,如果不是嚴謹的學術文章,不一定要分得那麼清楚。

上一篇
Class Methods
下一篇
大觀園的妙玉:Static Methods
系列文
Oops! OOPP: An Introduction to Object-Oriented Programming in Python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言